package io.gitee.chearnee.fw.openapi.processor;

import cn.hutool.crypto.SecureUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import io.gitee.chearnee.fw.openapi.annotation.AuthValidate;
import io.gitee.chearnee.fw.openapi.annotation.OpenApiAuth;
import io.gitee.chearnee.fw.openapi.business.BusinessManager;
import io.gitee.chearnee.fw.openapi.business.entity.Application;
import io.gitee.chearnee.fw.openapi.cache.ICacheManager;
import io.gitee.chearnee.fw.openapi.config.OpenApiProperties;
import io.gitee.chearnee.fw.openapi.constant.OpenApiContext;
import io.gitee.chearnee.fw.openapi.constant.OpenApiContextHolder;
import io.gitee.chearnee.fw.openapi.constant.enums.HeadersEnum;
import io.gitee.chearnee.fw.openapi.constant.enums.ResourcesTypeEnum;
import io.gitee.chearnee.fw.openapi.constant.enums.ResultCode;
import io.gitee.chearnee.fw.openapi.exception.OpenApiAsserts;
import io.gitee.chearnee.fw.openapi.filter.OpenApiHttpServletRequestWrapped;
import io.gitee.chearnee.fw.openapi.model.AuthParam;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;

import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;

/**
 * @author cn
 * @version 1.0
 * @description
 * @date 2021/9/23 14:23
 */
@Component
@AllArgsConstructor
@Slf4j
public class PreProcessor {

  private final OpenApiProperties openApiProperties;
  private final ObjectMapper objectMapper;
  private final ICacheManager iCacheManager;
  private final BusinessManager businessManager;

  /**
   * 必传header头
   *
   * @param request
   */
  public void checkHeaders(OpenApiHttpServletRequestWrapped request) {
    if (!openApiProperties.getHeaders().isEmpty()) {
      List<String> headers = openApiProperties.getHeaders();
      ImmutableSet<String> strings = ImmutableSet
          .copyOf(Iterators.forEnumeration(request.getHeaderNames()));
      Set<String> requestHeaders = new HashSet<>(strings);
      Set<String> mustHeaders = new HashSet<>(headers);
      Set<String> difference = Sets.difference(mustHeaders, requestHeaders);
      if (!difference.isEmpty()) {
        String next = difference.iterator().next();
        OpenApiAsserts.error(ResultCode.MISSING_PARAMETER,
                MessageFormat.format("请求头中缺少【{0}】参数", next));
      }
      for (String mustHeader : mustHeaders) {
        String header = request.getHeader(mustHeader);
        if (!StringUtils.hasText(header)) {
            OpenApiAsserts.error(ResultCode.PARAMETER_FAILED);
          return;
        }
      }
    }
  }

  /**
   * 检测签名
   *
   * @param request
   */
  @SneakyThrows
  public void checkSignature(OpenApiHttpServletRequestWrapped request) {
    HashMap<String, String> signatureHeaders = new HashMap<>(openApiProperties.getHeaders().size());
    if (!openApiProperties.getHeaders().isEmpty()) {
      openApiProperties.getHeaders().forEach(e -> signatureHeaders.put(e, request.getHeader(e)));
    }
    String bodyStr = null;
    bodyStr = request.getRequestParams();
    StringBuilder stringBuilder = new StringBuilder();
    if (StringUtils.hasText(bodyStr)) {
      Map<String, String> body = null;
      body = objectMapper
          .readValue(bodyStr, new TypeReference<Map<String, String>>() {
          });
      body.entrySet().removeIf(entry -> entry.getValue() == null);
      TreeMap<String, String> stringStringTreeMap = new TreeMap<>(body);
      stringStringTreeMap
          .forEach((key, value) -> stringBuilder.append(key).append("=").append(value).append("&"));
    }
    Application applicationByAppid = businessManager
        .getApplicationByAppid(signatureHeaders.get(HeadersEnum.APPID.getValue()));
    stringBuilder.append(HeadersEnum.APPID.getValue()).append("=")
        .append(signatureHeaders.get(HeadersEnum.APPID.getValue())).append("&");
    stringBuilder.append(HeadersEnum.TIMESTAMP.getValue()).append("=")
        .append(signatureHeaders.get(HeadersEnum.TIMESTAMP.getValue())).append("&");
    stringBuilder.append(HeadersEnum.NONCE_STRING.getValue()).append("=")
        .append(signatureHeaders.get(HeadersEnum.NONCE_STRING.getValue())).append("&");
    stringBuilder.append(HeadersEnum.APP_KEY.getValue()).append("=")
        .append(applicationByAppid.getAppKey());
    String signature = SecureUtil.md5(stringBuilder.toString()).toUpperCase();
    if (!Objects.equals(signature, signatureHeaders.get(HeadersEnum.SIGNATURE.getValue()))) {
        OpenApiAsserts.error(ResultCode.SIGN_VERIFICATION_FAILED);
    }
  }

  /**
   * 是否进入token验证
   *
   * @param handler
   * @return
   */
  public Boolean preAccessTokenHandler(Object handler) {
    if (handler instanceof HandlerMethod) {
      HandlerMethod handlerMethod = (HandlerMethod) handler;
      OpenApiAuth authHeader = handlerMethod.getMethodAnnotation(OpenApiAuth.class);
      if (authHeader == null) {
        authHeader = handlerMethod.getBeanType().getAnnotation(OpenApiAuth.class);
      }
      if (authHeader != null) {
        return !authHeader.ignoreAccessTokenAuth();
      }
    }
    return true;
  }

  /**
   * 检测token
   *
   * @param request
   */
  public void checkAccessToken(OpenApiHttpServletRequestWrapped request) {
    String appId = request.getHeader(HeadersEnum.APPID.getValue());
    String accessToken = request.getHeader(HeadersEnum.AUTHORIZATION.getValue());
    if (!StringUtils.hasText(accessToken)) {
        OpenApiAsserts.error(ResultCode.ACCESS_TOKEN_FAILED, "请传入token");
    }
    Boolean aBoolean = iCacheManager.verifyPassToken(appId, accessToken);
    if (Boolean.FALSE.equals(aBoolean)) {
        OpenApiAsserts.error(ResultCode.ACCESS_TOKEN_FAILED);
    }
  }

  /**
   * 重复请求
   *
   * @param request
   */
  public void checkRepeatRequest(OpenApiHttpServletRequestWrapped request) {
    String appId = request.getHeader(HeadersEnum.APPID.getValue());
    String nonceStr = request.getHeader(HeadersEnum.NONCE_STRING.getValue());
    if (Boolean.TRUE.equals(iCacheManager.repeatedRequest(appId, nonceStr))) {
        OpenApiAsserts.error(ResultCode.REQUEST_REPEATED);
    }
  }

  /**
   * 请求过期
   *
   * @param request
   */
  public void expiredRequest(OpenApiHttpServletRequestWrapped request) {
    String timestamp = request.getHeader(HeadersEnum.TIMESTAMP.getValue());
    LocalDateTime requestTime = null;
    try {
      requestTime = LocalDateTime
          .ofEpochSecond(Long.parseLong(timestamp), 0, ZoneOffset.ofHours(8));
    } catch (Exception e) {
        OpenApiAsserts.error(ResultCode.PARAMETER_FAILED);
    }
    Duration between = Duration.between(LocalDateTime.now(), requestTime);
    if (between.compareTo(openApiProperties.getRequestExpirationTimeOut()) > 0) {
        OpenApiAsserts.error(ResultCode.REQUEST_EXPIRED);
    }
  }

  public List<AuthParam> getAuthResource(String appId, Object[] paramObj) {
    return getAuthParam(appId, paramObj);
  }

  public void checkAuthResource(List<AuthParam> authParams) {
    List<Object> list = iCacheManager.hasAuthResource(authParams);
    if (list.isEmpty()) {
        OpenApiAsserts.error(ResultCode.PERMISSION_DENIED);
    }
    for (Object flag : list) {
      if (Objects.isNull(flag) || Boolean.FALSE.equals(flag)) {
          OpenApiAsserts.error(ResultCode.PERMISSION_DENIED);
      }
    }
  }

  /**
   * 获取参数中授权的资源
   *
   * @param paramObj
   * @return
   */
  private List<AuthParam> getAuthParam(String appId, Object[] paramObj) {
    if (!StringUtils.hasText(appId) || null == paramObj || paramObj.length == 0) {
      return Collections.emptyList();
    }
    StringBuilder sb = new StringBuilder();
    //按资源类型返回数据
    List<AuthParam> result = new ArrayList<>(paramObj.length);
    //参数对象
    for (Object obj : paramObj) {
      List<Field> fields = FieldUtils
          .getFieldsListWithAnnotation(obj.getClass(), AuthValidate.class);
      for (Field field : fields) {
        //获取属性所对应的注解信息
        AuthValidate annotation = field.getAnnotation(AuthValidate.class);
        field.setAccessible(true);
        ResourcesTypeEnum value = annotation.value();
        try {
          //获取属性值
          Object authValue = field.get(obj);
          if (null == authValue || !StringUtils.hasText(authValue.toString())) {
            continue;
          }
          sb.setLength(0);
          sb.append(iCacheManager.buildAuthResourceKey(appId, value));
          result.add(new AuthParam(sb.toString(), authValue.toString()));
        } catch (IllegalAccessException e) {
          log.error("获取对象属性值异常:{}", obj, e);
        }
      }
    }
    return result;
  }

  public void initContext(OpenApiHttpServletRequestWrapped request) {
    String appid = request.getHeader(HeadersEnum.APPID.getValue());
    if (StringUtils.hasText(appid)) {
      OpenApiContext openApiContext = new OpenApiContext();
      openApiContext.setAppId(request.getHeader(HeadersEnum.APPID.getValue()));
      OpenApiContextHolder.setOpenApiContext(openApiContext);
    }
  }

  public void disposeContext() {
    OpenApiContextHolder.remove();
  }
}
